/******************************************************************************/
/*!
\file   Matrix.hpp
\author M.J. Gardner
\date   12/2/2011
\brief
      templatized n-by-m matrix math library, with row/column iterators and all
      kinds of fun matrix math. Has appropriate typedefs and functions to work
      for vectors as well.
      Matrix iterators, row iterators, and column iterators are all random-access
      iterators.

Things you can do with a matrix:
  -assign, including compound assignment
  -compare for equality
  -add/subtract 2 matrices
  -multiply/divide by scalars
  -multiply an AxB matrix with a BxC matrix (yields an AxC matrix)
  -negate all elements of a matrix
  -fill with 0s
  -transpose
  -tensor product
  -get the determinant
  -iterate over elements
  -iterate over a row
  -iterate over a column
  -access specific elements
  -get number of rows
  -get number of columns
  -output as text

Square matrices only:
  -make Identity matrix
  -make Scale matrix
  -make Translation matrix
  -get the Trace (sum of diagonal entries)

Specific sizes of square matrices only:
  -make rotation matrix about an arbitrary axis (axis expressed as normalized vector)

Vectors only:
  -magnitude/square-magnitude
  -distance/square-distance
  -inner product (aka dot product)
  -normalize
  -get dimension (same as Rows)

Vec3 only:
  -cross product

*/
/******************************************************************************/
#ifndef MATRIX_TEMPLATE_HPP
#define MATRIX_TEMPLATE_HPP

#include "MathUtil.hpp"     //epsilon
#include <ostream>          //output
#include <initializer_list>
#include <array>


//R = rows, C = columns
template<unsigned int R = 3, unsigned int C = 3, typename T = float>
struct Matrix
{
  //can default construct, copy construct, construct element-wise,
  //from std::arrays with or without providing iterators to them, construct
  //from 1 or 2 dimensional initializer list, and construct from 1 or 2 dimensional
  //old-style arrays.
  Matrix(void);
  template<unsigned int A, unsigned int B>
  Matrix(const Matrix<A, B, T> &rhs);
  Matrix(const std::array<T, C*R> &rhs);
  Matrix(const std::array<std::array<T, C>, R> &rows);
//  Matrix(const std::array<std::array<T, R>, C> &cols);
  Matrix(const typename std::array<T, C*R>::iterator &begin_it,
          const typename std::array<T, C*R>::iterator &end_it);
  Matrix(const typename std::array<std::array<T, C>, R>::iterator &r_begin,
          const typename std::array<std::array<T, C>, R>::iterator &r_end);
//  Matrix(const typename std::array<std::array<T, R>, C>::iterator &c_begin,
//          const typename std::array<std::array<T, R>, C>::iterator &c_end);
  Matrix(const std::initializer_list<T> &rhs);
  Matrix(const std::initializer_list<std::initializer_list<T>> &rhs);
  Matrix(T *rhs);
  Matrix(T **rhs);


  //random access iterator that treats the elements as contiguous
  struct iterator
  {
      iterator(const typename std::array<T, R*C>::iterator &it);
      iterator(const typename Matrix<R, C, T>::iterator &it);
      ~iterator();

      //all other comparison operators exist as non-members
      bool operator==(const typename Matrix<R, C, T>::iterator &rhs);
      bool operator<(const typename Matrix<R, C, T>::iterator &rhs);
      //can pre and post increment and decrement
      iterator &operator++(void);
      iterator operator++(int);
      iterator &operator--(void);
      iterator operator--(int);
      //assignment and compound assignment
      iterator &operator=(const typename Matrix<R, C, T>::iterator &rhs);
      iterator &operator+=(const int rhs);
      iterator &operator-=(const int rhs);
      //iterator-from-iterator subtraction (other addition/subtraction operators
      //are non-member)
      int operator-(const typename Matrix<R, C, T>::iterator &rhs);
      //dereference
      T &operator*(void);
      //offset dereference
      T &operator[](const int offset);

  private:
    typename std::array<T, R*C>::iterator _it;
  };

  //random access iterator along the elements of a row
  struct row_iter
  {
      row_iter(const typename std::array<T, C>::iterator &it);
      row_iter(const typename Matrix<R, C, T>::row_iter &it);
      ~row_iter();

      //all other comparison operators exist as non-members
      bool operator==(const typename Matrix<R, C, T>::row_iter &rhs);
      bool operator<(const typename Matrix<R, C, T>::row_iter &rhs);
      //can pre and post increment and decrement
      row_iter &operator++(void);
      row_iter operator++(int);
      row_iter &operator--(void);
      row_iter operator--(int);
      //assignment and compound assignment
      row_iter &operator=(const typename Matrix<R, C, T>::row_iter &rhs);
      row_iter &operator+=(const int rhs);
      row_iter &operator-=(const int rhs);
      //iterator-from-iterator subtraction (other addition/subtraction operators
      //are non-member)
      int operator-(const typename Matrix<R, C, T>::row_iter &rhs);
      //dereference
      T &operator*(void);
      //offset dereference
      T &operator[](const int offset);

  private:
    typename std::array<T, C>::iterator _it;
  };

  //random access iterator along the elements of a column
  struct col_iter
  {
      col_iter(const unsigned int col,
                const typename std::array<std::array<T, C>, R>::iterator &it);
      col_iter(const typename Matrix<R, C, T>::col_iter &it);
      ~col_iter();

      //all other comparison operators exist as non-members
      bool operator==(const typename Matrix<R, C, T>::col_iter &rhs);
      bool operator<(const typename Matrix<R, C, T>::col_iter &rhs);
      //can pre and post increment and decrement
      col_iter &operator++(void);
      col_iter operator++(int);
      col_iter &operator--(void);
      col_iter operator--(int);
      //assignment and compound assignment
      col_iter &operator=(const typename Matrix<R, C, T>::col_iter &rhs);
      col_iter &operator+=(const int rhs);
      col_iter &operator-=(const int rhs);
      //iterator-from-iterator subtraction (other addition/subtraction operators
      //are non-member)
      int operator-(const typename Matrix<R, C, T>::col_iter &rhs);
      //dereference
      T &operator*(void);
      //offset dereference
      T &operator[](const int offset);

  private:
    const unsigned int _col_num;
    typename std::array<std::array<T, C>, R> &_it;
  };


  //subscript/element access
  T &operator()(const unsigned int row, const unsigned int column);
  const T &operator()(const unsigned int row, const unsigned int column) const;
  // std::array<T, M> &operator[](const unsigned int row);
  // const std::array<T, M> &operator[](const unsigned int row) const;
  T &operator[](const unsigned int index);
  const T &operator[](const unsigned int index) const;

  //iterator getters for all 3 kinds of iterator
  typename Matrix<R, C, T>::iterator begin(void);
  typename Matrix<R, C, T>::iterator end(void);
  typename Matrix<R, C, T>::row_iter row_begin(unsigned int row_num);
  typename Matrix<R, C, T>::row_iter row_end(unsigned int row_num);
  typename Matrix<R, C, T>::col_iter col_begin(unsigned int col_num);
  typename Matrix<R, C, T>::col_iter col_end(unsigned int col_num);


  //assignment and compound assignment operators
  template<unsigned int A, unsigned int B>
  Matrix<R, C, T> &operator=(const typename Matrix<A, B, T> &rhs);
  Matrix<R, C, T> &operator*=(const T rhs);
  Matrix<R, C, T> &operator/=(const T rhs);
  Matrix<R, C, T> &operator+=(const Matrix<R, C, T> &rhs);
  Matrix<R, C, T> &operator-=(const Matrix<R, C, T> &rhs);
  //unary negation negates each element
  Matrix<R, C, T> operator-(void) const;


  //get number of rows
  unsigned int Rows(void);
  //get number of columns
  unsigned int Columns(void);


  void FromArray(T *rhs);
  void FromArray(T **rhs);

private:

  union
  {
    std::array<std::array<T, C>, R> _rows;
    std::array<T, R*C> _elems;
  };

  template<unsigned int N, unsigned int M>
  void CopyElemHelper(const Matrix<N, M, T> &rhs);
};  //struct Matrix





//aliases for common vectors and matrices
typedef Matrix<2, 1, float> Vec2;
typedef Matrix<3, 1, float> Vec3;
typedef Matrix<4, 1, float> Vec4;
typedef Matrix<2, 2, float> Mat2;
typedef Matrix<3, 3, float> Mat3;
typedef Matrix<4, 4, float> Mat4;

//TODO: when GCC 4.7 releases, update to it and switch to the alias templates
//commented out below
#define MTX_IT typename Matrix<R, C, T>::iterator
#define ROW_IT typename Matrix<R, C, T>::row_iter
#define COL_IT typename Matrix<R, C, T>::col_iter
// //alias templates are part of the C++11 specification, but they're not
// //in GCC yet
// template<unsigned int R, unsigned int C, typename T>
// using Mtx_It = Matrix<R, C, T>::iterator;


//matrix math and iterator functions that don't have to be members or friends

/*
----these work with all matrices----
*/
//comparison
template<unsigned int R, unsigned int C, typename T>
bool operator==(const Matrix<R, C, T> &lhs, const Matrix<R, C, T> &rhs);
template<unsigned int R, unsigned int C, typename T>
bool operator!=(const Matrix<R, C, T> &lhs, const Matrix<R, C, T> &rhs);
//scalar multiply/divide
template<unsigned int R, unsigned int C, typename T>
Matrix<R, C, T> operator*(const Matrix<R, C, T> &lhs, const T &rhs);
template<unsigned int R, unsigned int C, typename T>
Matrix<R, C, T> operator*(const T &lhs, const Matrix<R, C, T> &rhs);
template<unsigned int R, unsigned int C, typename T>
Matrix<R, C, T> operator/(const Matrix<R, C, T> &lhs, const T &rhs);
//addition/subtraction
template<unsigned int R, unsigned int C, typename T>
Matrix<R, C, T> operator+(const Matrix<R, C, T> &lhs, const Matrix<R, C, T> &rhs);
template<unsigned int R, unsigned int C, typename T>
Matrix<R, C, T> operator-(const Matrix<R, C, T> &lhs, const Matrix<R, C, T> &rhs);
//matrix multiplication
template<unsigned int A, unsigned int M, unsigned int B, typename T>
Matrix<A, B, T> operator*(const Matrix<A, M, T> &lsh, const Matrix<M, B, T> &rhs);
//tensor product of 2 matrices
template<unsigned int RL, unsigned int CL, unsigned int RR, unsigned int CR, typename T>
Matrix<RL*RR, CL*CR, T> Tensor(const Matrix<RL, CL, T> &lhs, const Matrix<RR, CR, T> &rhs);
//fill a matrix with all 0s
template<unsigned int R, unsigned int C, typename T>
void ZeroMtx(Matrix<R, C, T> &mtx);
//get the transpose of a matrix
template<unsigned int R, unsigned int C, typename T>
Matrix<C, R, T> Transpose(const Matrix<R, C, T> &mtx);
//get determinant of a matrix
template<unsigned int R, unsigned int C, typename T>
T Determinant(const Matrix<R, C, T> &mtx);


/*
----these work with square matrices only----
*/
//set a square matrix to an Identity matrix
template<unsigned int N, typename T>
void IdentityMtx(Matrix<N, N, T> &mtx);
//set a square matrix to a scale matrix
template<unsigned int N, typename T>
void ScaleMtx(Matrix<N, N, T> &mtx, Matrix<N, 1, T> &scale_vec);
//set a square matrix to a translation matrix
template<unsigned int N, typename T>
void TranslationMtx(Matrix<N, N, T> &mtx, Matrix<N, 1, T> &trans_vec);
//get the trace of a matrix (sum of members on the diagonal)
template<unsigned int R, unsigned int C, typename T>
T Trace(const Matrix<R, C, T> &mtx);

/*
----these work with specific sizes of square matrices only----
*/
//2 dimensional rotation
void RotationMtx(Mat2 &mtx, float theta);
//3 dimensional rotation
void RotationMtx(Mat3 &mtx, Vec3 &axis, float theta);
//3 dimensional rotation for use with vec4s
void RotationMtx(Mat4 &mtx, Vec3 &axis, float theta);


/*
----these work with vectors only----
*/
//Dot product
template<unsigned int N, typename T>
T InnerProduct(const Matrix<N, 1, T> &lhs, const Matrix<N, 1, T> &rhs);
//Magnitude (length of the vector)
template<unsigned int N, typename T>
T Magnitude(const Matrix<N, 1, T> &vec);
//squared magnitude. Cheaper than Magnitude because it doesn't call sqrt(), so
//use this if you just want to compare magnitudes.
template<unsigned int N, typename T>
T MagnitudeSquared(const Matrix<N, 1, T> &vec);
//distance - the magnitude of the difference between 2 vectors
template<unsigned int N, typename T>
T Distance(const Matrix<N, 1, T> &lhs, const Matrix<N, 1, T> &rhs);
//squared distance. Doesn't need to call sqrt, so use this version if you
//only want to compare distances.
template<unsigned int N, typename T>
T DistanceSquared(const Matrix<N, 1, T> &lhs, const Matrix<N, 1, T> &rhs);
//get a normalized version of a vector. That is, a vector with the same direction,
//but magnitude 1. This version leaves the vector intact and returns a normalized copy
template<unsigned int N, typename T>
Matrix<N, 1, T> Normalize(const Matrix<N, 1, T> &vec);
//normalize in place. This version actually alters the contents of its argument,
//rather than returning a normalized copy
template<unsigned int N, typename T>
Matrix<N, 1, T> NormalizeInPlace(Matrix<N, 1, T> &vec);
//the dimension of a vector is the same as its number of rows
template<unsigned int N, typename T>
unsigned int Dimension(const Matrix<N, 1, T> &vec);


/*
----these work with 3-dimensional vectors only----
*/
//cross product gets a vector perpendicular to both arguments.
//this is the right-handed version of cross product.
//flipping the order of the arguments will invert the direction of the result.
Vec3 CrossProduct(const Vec3 &lhs, const Vec3 &rhs);


/*
Matrix::iterator nonmember functions
*/
// full set of comparisons (== and < are members)
template<unsigned int R, unsigned int C, typename T>
bool operator!=(const MTX_IT &lhs, const MTX_IT &rhs);
template<unsigned int R, unsigned int C, typename T>
bool operator>(const MTX_IT &lhs, const MTX_IT &rhs);
template<unsigned int R, unsigned int C, typename T>
bool operator<=(const MTX_IT &lhs, const MTX_IT &rhs);
template<unsigned int R, unsigned int C, typename T>
bool operator>=(const MTX_IT &lhs, const MTX_IT &rhs);
//add ints, subtract ints or iterators (iterator-from-iterator subtraction is a member)
template<unsigned int R, unsigned int C, typename T>
MTX_IT operator+(const MTX_IT &lhs, const int rhs);
template<unsigned int R, unsigned int C, typename T>
MTX_IT operator+(const int lhs, const MTX_IT &rhs);
template<unsigned int R, unsigned int C, typename T>
MTX_IT operator-(const MTX_IT &lhs, const int rhs);

/*
Matrix::row_iter nonmember functions
*/
// full set of comparisons (== and < are members)
template<unsigned int R, unsigned int C, typename T>
bool operator!=(const ROW_IT &lhs, const ROW_IT &rhs);
template<unsigned int R, unsigned int C, typename T>
bool operator>(const ROW_IT &lhs, const ROW_IT &rhs);
template<unsigned int R, unsigned int C, typename T>
bool operator<=(const ROW_IT &lhs, const ROW_IT &rhs);
template<unsigned int R, unsigned int C, typename T>
bool operator>=(const ROW_IT &lhs, const ROW_IT &rhs);
//add ints, subtract ints or iterators (iterator-from-iterator subtraction is a member)
template<unsigned int R, unsigned int C, typename T>
ROW_IT operator+(const ROW_IT &lhs, const int rhs);
template<unsigned int R, unsigned int C, typename T>
ROW_IT operator+(const int lhs, const ROW_IT &rhs);
template<unsigned int R, unsigned int C, typename T>
ROW_IT operator-(const ROW_IT &lhs, const int rhs);

/*
Matrix::col_iter nonmember functions
*/
// full set of comparisons (== and < are members)
template<unsigned int R, unsigned int C, typename T>
bool operator!=(const COL_IT &lhs, const COL_IT &rhs);
template<unsigned int R, unsigned int C, typename T>
bool operator>(const COL_IT &lhs, const COL_IT &rhs);
template<unsigned int R, unsigned int C, typename T>
bool operator<=(const COL_IT &lhs, const COL_IT &rhs);
template<unsigned int R, unsigned int C, typename T>
bool operator>=(const COL_IT &lhs, const COL_IT &rhs);
//add ints, subtract ints or iterators (iterator-from-iterator subtraction is a member)
template<unsigned int R, unsigned int C, typename T>
COL_IT operator+(const COL_IT &lhs, const int rhs);
template<unsigned int R, unsigned int C, typename T>
COL_IT operator+(const int lhs, const COL_IT &rhs);
template<unsigned int R, unsigned int C, typename T>
COL_IT operator-(const COL_IT &lhs, const int rhs);




#include "Matrix.inl"

#endif
